#include <fstream>
#include "ProjectHeader.h"
#include "CatalogueLoader.h"
#define DEFAULTCUBE  0

GLfloat						CatalogueLoader::static_GridSpace = 0;
std::string					CatalogueLoader::LastCatalogue = "";
std::string					CatalogueLoader::static_UilFile = "";
std::string					CatalogueLoader::static_SceneFile = "";
std::map<std::string, int>	CatalogueLoader::static_TexIDs;
std::vector<std::string>	CatalogueLoader::static_TaiFiles;
std::map<int, Cube_Prototype> CatalogueLoader::static_Cubes;

void		CatalogueLoader::TrimString(std::string& Str)
{
	while(!Str.empty() && (Str[0] == ' ' || Str[0] == '\t')){Str.erase(0, 1);}
}

std::string	CatalogueLoader::GetSubStr(std::string& Str, const char Seperator)
{
	unsigned int i = Str.find(Seperator); if(i == Str.npos || i == 0){return Str;}
	std::string ReturnString = Str.substr(0, i);
	Str.erase(0, i + 1); TrimString(Str); 
	//erase the seperator too, and any free space following it
	return ReturnString;
}

int			CatalogueLoader::LoadTextureAtlases(const char *Catalogue, const TextureLoader_delegate LoadTexture, std::map<int, texCoord2f>& TexMap)
{
	std::string Temp; 
	int ReturnValue = ALL_OK;
	Temp.assign(Catalogue);
	if(LastCatalogue != Temp)
	{
		LastCatalogue = Temp; 
		ReturnValue = CatParser(Catalogue);
		if(ReturnValue){return ReturnValue;}
		LastCatalogue.assign(Catalogue);
	}
	ReturnValue = TaiParser(static_TaiFiles, static_TexIDs, TexMap, LoadTexture);
	return ReturnValue;
}

int			CatalogueLoader::LoadScene(const char * Catalogue, std::vector<std::vector<CUBE_PROTO_INDEX>>& CubeMap, std::map<int, Cube_Prototype>& Cubes, GLfloat& GridSize)
{
		std::string Temp; 
		int ReturnValue = ALL_OK;
		Temp.assign(Catalogue);
		if(LastCatalogue != Temp)
		{
			LastCatalogue = Temp; 
			ReturnValue = CatParser(Catalogue);
			if(ReturnValue){return ReturnValue;}
			LastCatalogue.assign(Catalogue);
		}
		ReturnValue = ScnParser(static_SceneFile.c_str(), static_Cubes, CubeMap);
		Cubes = static_Cubes;
		return ReturnValue;
}
int			CatalogueLoader::LoadUILayout(const char * Catalogue, std::vector<UI_FILE_ELEMENT> & UilVector)
{
	std::string Temp;
	int ReturnValue = ALL_OK;
	Temp.assign(Catalogue);
	if(LastCatalogue != Temp)
	{
		LastCatalogue = Temp;
		ReturnValue = CatParser(Catalogue);
		if(ReturnValue){return ReturnValue;}
		LastCatalogue.assign(Catalogue);
	}
	ReturnValue = UilParser(static_UilFile.c_str(), UilVector);
	return ReturnValue;
}
int			CatalogueLoader::CatParser(const char * Catalogue)
{
	
	std::ifstream CatalogueFile(Catalogue);	
	if(!CatalogueFile.good()){return NULL_FILE_ERROR;}	
	//If the file doesn't open properly, return an error
	CatalogueFile.seekg(0, std::ios::beg);
	std::string BufferStr;
	while(std::getline(CatalogueFile, BufferStr))	//Getline returns false if the file's all been read
	{
		TrimString(BufferStr);				//Remove whitespace at the start of the line.
		if(BufferStr.empty()){continue;}	//If the line was empty, go to the next one
		if(BufferStr[0] == '#'){continue;}	//If the line was a comment, go to the next one
		if(BufferStr.size() > 8 && BufferStr.substr(0, 8) == "gridsize")
		{
			BufferStr.erase(0, 8); TrimString(BufferStr);
			BufferStr.erase(0, strspn(BufferStr.c_str(), " ,="));
			static_GridSpace = atof(BufferStr.c_str());
			continue;
		}
		if(BufferStr.size() > 5 && BufferStr.substr(0, 5) == "scene")
		{
			BufferStr.erase(0,5); TrimString(BufferStr);	//Remove "scene" and trim
			static_SceneFile = BufferStr;							//set SceneFile
			continue;
		}	
		if(BufferStr.size() > 4 && BufferStr.substr(0, 4) == "cube")
		{
			BufferStr.erase(0,4); TrimString(BufferStr); //Trim off the "cube"
			Cube_Prototype TempCube;
			unsigned int CubeIndex = atoi(BufferStr.c_str()); 
			GetSubStr(BufferStr, '='); //Removes cube number & the = sign;
			//eg " 1 = 0, 2, 4, 3, 2..." -> "0, 2, 4, 3, 2..."
			TempCube.CantTouchThis = false; TempCube.Dangerous = false; TempCube.Drawable = false;
			//Preset the values of all the bools to false
			for(int i = 0; i < 6; i++) {TempCube.Texture[i] = atoi(GetSubStr(BufferStr, ',').c_str());}
			//Load up the texture ids
			std::string TempStr;
			TempStr = GetSubStr(BufferStr, ','); //get the bool value as a string - true/false
			if(TempStr != "true" && TempStr != "false"){continue;}	//if it's neither, skip this cube
			if(TempStr == "true"){TempCube.Drawable = true;} 
			//if it's true, set true. otherwise, it's false, and we already set all the bools to that.
			TempStr = GetSubStr(BufferStr, ',');
			if(TempStr != "true" && TempStr != "false"){continue;}
			if(TempStr == "true"){TempCube.Dangerous = true;}
			TempStr = GetSubStr(BufferStr, ',');
			if(TempStr != "true" && TempStr != "false"){continue;}
			if(TempStr == "true"){TempCube.CantTouchThis = true;}
			static_Cubes[CubeIndex] = TempCube;
			continue;
		}
		if(BufferStr.size() > 5 && BufferStr.substr(0, 5) == "using")
		{
			BufferStr.erase(0,5); TrimString(BufferStr);
			//Remove the 'using' piece, and any whitespace that follows
			if(!BufferStr.empty()){static_TaiFiles.push_back(BufferStr);} 
			//And put the Tai file name into the list if the string isn't empty
			continue; //Now go to the next one
		}
		if(BufferStr.size() > 7 && BufferStr.substr(0,7) == "texture")
		{
			BufferStr.erase(0,7); TrimString(BufferStr);
			int i = strspn(BufferStr.c_str(), "1234567890");
			if(i)	//If the string begins with a numeric value
			{
				int Tex = atoi(BufferStr.substr(0, i).c_str());
				BufferStr.erase(0, i);
				BufferStr.erase(0, strspn(BufferStr.c_str(), " ,="));	//Remove delimiting characters
				static_TexIDs[BufferStr] = Tex;	//Set the name associated with the texID to the remaining characters
				continue;
			}
		}
		if(BufferStr.size() > 7 && BufferStr.substr(0,7) == "uilfile")
		{
			BufferStr.erase(0,7); TrimString(BufferStr);
			if(!BufferStr.empty()){static_UilFile = BufferStr;}
		}
	}
	CatalogueFile.close();
	return ALL_OK;
}

int			CatalogueLoader::TaiParser(const std::vector<std::string>& TaiFiles, std::map<std::string,int>& TexIDs, std::map<int, texCoord2f>& TexMap, const TextureLoader_delegate LoadTexture)
{
		std::ifstream TaiFile;
		std::string BufferStr;
		std::map<std::string, int> TexIndexes;	//Stores the indexes into texture[] of each one loaded, along with the filename
		std::map<std::string, bool> LoadedTextures;	//Keys are filenames of texures, value is whether it's loaded or not.
		
	for(unsigned int i = 0; i < TaiFiles.size(); i++)
	{
		TaiFile.open(TaiFiles[i].c_str());
		if(!TaiFile.good()){continue;}	//If the file doesn't open properly, try the next one
		TaiFile.seekg(0, std::ios::beg);
		while(getline(TaiFile, BufferStr))
		{
			TexParseData currentTex;
			TrimString(BufferStr);	
			//Remove whitespace at the start of the line. Stop if the string is empty
			if(BufferStr.empty()){continue;}	//If the line was empty, go to the next one
			if(BufferStr[0] == '#'){continue;}	//If the line was a comment, go to the next one
			//it's not a comment, so it's a filename followed by two tabs; So, we look for the tabs.
			
			currentTex.TexName = GetSubStr(BufferStr, '\t');

			if(TexIDs.find(currentTex.TexName) == TexIDs.end()){continue;}
			//If this texture isn't required, skip over it.
			currentTex.TexFile = GetSubStr(BufferStr, ',');
			currentTex.index = atoi(GetSubStr(BufferStr, ',').c_str());
			GetSubStr(BufferStr, ','); //Ignore texture type; should always be 2d
			currentTex.TexCoords.topLft.u = atof(GetSubStr(BufferStr, ',').c_str());
			currentTex.TexCoords.topLft.v = atof(GetSubStr(BufferStr, ',').c_str());
			GetSubStr(BufferStr, ','); //Ignore Depth; Should always be 0, and is only important for 3d textures.
			float width = atof(GetSubStr(BufferStr, ',').c_str());
			float height = atof(BufferStr.c_str());
			//BufferStr should now be just the last value, which is not followed by any delimiting characters
			//This is why there's no GetSubStr for height; it is the last part of the string

			currentTex.TexCoords.topRgt.u = currentTex.TexCoords.topLft.u + width;
			currentTex.TexCoords.topRgt.v = currentTex.TexCoords.topLft.v;
			currentTex.TexCoords.btmLft.u = currentTex.TexCoords.topLft.u;
			currentTex.TexCoords.btmLft.v = currentTex.TexCoords.topLft.v + height;
			currentTex.TexCoords.btmRgt.u = currentTex.TexCoords.topLft.u + width;
			currentTex.TexCoords.btmRgt.v = currentTex.TexCoords.topLft.v + height;

			if(!LoadedTextures[currentTex.TexFile])
			{
				TexIndexes[currentTex.TexFile] = LoadTexture(currentTex.TexFile.c_str());
				if(TexIndexes[currentTex.TexFile] < 0)
				{
					return TexIndexes[currentTex.TexFile];
				}	
				//LoadTexture returns either a tex pointer, or an error code < 0.
				//If we get an error loading, we return that error.
				LoadedTextures[currentTex.TexFile] = true;
			}
			//If the texture isn't loaded, load it and set the maps so that we'll know that it's loaded
			//If we look for it again.

			currentTex.TexCoords.TexIndex = TexIndexes[currentTex.TexFile];

			TexMap[ TexIDs.find(currentTex.TexName)->second ] = currentTex.TexCoords;
			//TexIDs.find(currentTex.TexName) will always give an iterator; otherwise, the if statement
			//That checks for textures to load would have skipped over this one. It's being loaded
			//BECAUSE it exists in TexIDs.

			TexIDs.erase(TexIDs.find(currentTex.TexName));
			//Remove this texture from TexIDs; We've loaded it already, so we don't need to find it again.
		}
		TaiFile.close();
		TaiFile.clear();
		//Clear any error attributes set, so the next one doesn't seem to be bad;
		//Error attributes aren't reset by closing a file or by opening another.
	}
	return ALL_OK;
}
int			CatalogueLoader::ScnParser(const char* SceneFile, const std::map<int, Cube_Prototype>& Cubes, std::vector<std::vector<CUBE_PROTO_INDEX>>& CubeMap)
{
	std::ifstream Scene(SceneFile);
	if(!Scene.good()){return NULL_FILE_ERROR;}
	Scene.seekg(0, std::ios::beg);
	std::string BufferStr;
	int xSize = 0;
	int ySize = 0;
	int y = 0;
	while(std::getline(Scene, BufferStr))
	{
		
		TrimString(BufferStr); if(!BufferStr.size()){continue;}	
		//Skip if the string is empty; stops us accessing invalid elements - eg BufferStr[0].
		if(BufferStr[0] == '#'){continue;}
		if(BufferStr[0] == 'x' && !xSize)
		{
			GetSubStr(BufferStr, '=');
			xSize = atoi(BufferStr.c_str()); 
			if(xSize && ySize){break;}
			continue;
		}
		if(BufferStr[0] == 'y' && !ySize)
		{
			GetSubStr(BufferStr, '=');
			ySize = atoi(BufferStr.c_str());
			if(xSize && ySize){break;}
			continue;
		}
	}
	if(!(xSize && ySize)){return INCOMPLETE_SCENE_HEADER;}	
	//The first while loop looks for scene header information; The size of the scene, and in future, the default cube.
	CubeMap.resize(xSize);
	for(int x = 0; x < xSize; x++){CubeMap[x].resize(ySize);}

	while(std::getline(Scene, BufferStr))
	{
		TrimString(BufferStr); if(!BufferStr.size()){continue;}
		if(BufferStr[0] == '#'){continue;}
		for(int x = 0; x < xSize; x++)
		{
			if(BufferStr.size())
			{
				CubeMap[x][y] = atoi(GetSubStr(BufferStr, ',').c_str());
				if(Cubes.find(CubeMap[x][y]) == Cubes.end()){CubeMap[x][y] = DEFAULTCUBE;}
				//TODO: Put in a default cube parameter in the scene file
				//if the cube isn't in the prototype list, set it to the default.
				//NOTE: An error could be raised here instead perhaps
			}
			else
			{
				for(;x < xSize; x++){CubeMap[x][y] = DEFAULTCUBE;}
				//If the row ends prematurely, fill up the empty places with the default cube, 0.
				//it also puts x equal to xSize - 1, which breaks out of the loop.
				//TODO: Put in a default cube parameter in the scene file
			}
		}
		y++; if(y == ySize){break;}
		//Stop if we're read in all the rows.
	}
	if(y != ySize)
	{
		//There's two ways we could have broken out of the while loop;
		//either we reached the end of the file (bad), or the end of the scene (good)
		//but we're here, so obviously we ran out of file first.
		for(; y < ySize; y++)
		{
			for(int x = 0; x < xSize; x++)
			{
				CubeMap[x][y] = 0;
			}
		}
		//So, Just fill all the blanks with the default cube, 0. 
		//TODO: Put a default cube parameter in the scene file header
	}
	Scene.close();
	return ALL_OK;
}

int			CatalogueLoader::UilParser(const char* UilFile, std::vector<UI_FILE_ELEMENT>& UilVector)
{
	std::ifstream UI(UilFile);
	if(!UI.good()){return NULL_FILE_ERROR;}
	UI.seekg(0, std::ios::beg);
	std::string BufferStr;
	std::string Type_Specific_Numbers;
	std::string Type_Specific_String;
		UI_FILE_ELEMENT TempUIElement;
	while(std::getline(UI, BufferStr))
	{
		TrimString(BufferStr);	//Get rid of spaces at the start
		if(BufferStr[0] == '#')	//If the line starts with a #, it's a comment.
		{
			continue;
		}
		if(BufferStr.substr(0, 9) == "UIElement")	//Parse UIElements here...
		{
			BufferStr.erase(0, 9);	//Remove the UIElement prefix
			TrimString(BufferStr);	//And the spaces after it
			TempUIElement.BGTexture = (unsigned int)atoi(GetSubStr(BufferStr, ',').c_str());
			TempUIElement.btmLft.u = atof(GetSubStr(BufferStr, ',').c_str());
			TempUIElement.btmLft.v = atof(GetSubStr(BufferStr, ',').c_str());
			TempUIElement.size.u = atof(GetSubStr(BufferStr, ',').c_str());
			TempUIElement.size.v = atof(GetSubStr(BufferStr, ',').c_str());
			Type_Specific_Numbers = GetSubStr(BufferStr, ',').c_str();
			TempUIElement.TypeSpecificString = BufferStr;

			Type_Specific_Numbers.erase(0,1);	//Delete the '[' at the start of the list
			while(Type_Specific_Numbers.size())	//Until we read all of it
			{
				unsigned int CurrentNum = (unsigned int)atoi(GetSubStr(Type_Specific_Numbers, ',').c_str());
				TempUIElement.TypeSpecificNumbers.push_back(CurrentNum);
			}
		UilVector.push_back(TempUIElement);
			
		}
	}
}